iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0

最近剛好有在看 Clean Architecture 這本書,裡面有提到大名鼎鼎的 SOLID 原則,雖然還沒有完全看完,但今天可以先來聊聊這五大原則。

What is SOLID

這是由 Robert C. Martin 所提出的物件導向設計中的五大原則,目的在於使設計的系統更易於維護和擴展。
SOLID 分別是由以下五個原則所組成:

  1. Single Responsibility Principle, SRP
  2. Open/Closed Principle, OCP
  3. Liskov Substitution Principle, LSP
  4. Interface Segregation Principle, ISP
  5. Dependency Inversion Principle, DIP

單一職責原則 (Single Responsibility Principle, SRP)

A class should have one and only one reason to change, meaning that a class should have only one job
每個 class 只專注負責一個功能,並且這個功能要完全封裝在這個 class 中。簡而言之,類別應該只有一個修改它的理由。
一個修改他的理由伴隨著的都是使用者,因此我們也可以說一個模組應該只對唯一一個角色負責,必須分開不同角色所依賴的程式碼。
優點

  • 有助於提高程式的可讀性與可維護性,並且當角色有需求需要更改功能時,只需修改該 class 相關的功能,減少修改可能造成的風險。
class Account:
    def __init__(self, name):
        self.name = name
        self.balance = 0

    def deposit(self, amount):
        self.balance += amount
        return f"Deposited {amount}, new balance: {self.balance}"

    def withdraw(self, amount):
        if amount > self.balance:
            return "Insufficient funds"
        self.balance -= amount
        return f"Withdrawn {amount}, new balance: {self.balance}"
    
    def change_name(self, name):
        self.name = name 

上述 class 違反 SRP:
存錢與領錢:與帳戶餘額有關。
改名:與帳戶資料管理有關。
兩者對應的角色不同,其依賴的方法需要分開。

開放封閉原則 (Open/Closed Principle, OCP)

Objects or entities should be open for extension but closed for modification.
類別應該對擴展開放,但對修改封閉。簡而言之,就是可以通過擴展類別(例如繼承)來增加新功能,而不是去修改現有的類別。

優點:

  • 當需求變更時,可以通過擴展來應對,而不會破壞現有系統的穩定性。
class Animal:
    @classmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

class AnimalSoundPrinter:
    def print_sound(self, animal):
        print(animal.speak())

dog = Dog()
cat = Cat()

printer = AnimalSoundPrinter()
printer.print_sound(dog)
printer.print_sound(cat)

若要得知動物的聲音,不需要修改 AnimalSoundPrinter,只需要再新增一種動物的類別即可。

里氏替換原則 (Liskov Substitution Principle, LSP)

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T
子型別的物件應該要能夠替代父型別的物件,且不影響程式的正確性

優點:

  • 這保證了類別之間的行為一致性,使用父類的地方應該也能替換成使用子類,系統才能保持穩定。
class Person:
    first_name: str
    last_name: str
    
    def display_name(self):
        print(self.last_name + self.first_name)
        
class Employee(Person):
    first_name: int
    last_name: int
    
     def display_name(self):
        print(self.first_name + self.last_name)

這裡有兩個地方違反 LSP,

  1. name 的 type 父類子類不同不能直接替代
  2. 就算都是 str, 印出名稱的順序不同,也同樣導致不能相互替代,因為計算的邏輯不一致。

介面隔離原則 (Interface Segregation Principle, ISP)

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.
不應強迫使用者依賴他們不需要或不使用的介面。換言之,應將龐大的介面拆解為更小、更專門的介面,以滿足不同的使用者的需求。

書中有個例子我覺得很好解釋: 若包包中攜帶了你不需要的東西,你卻要依賴這個包包來做事,可能會導致意想不到的麻煩。

優點:

  • 避免多餘的依賴項,讓 class 只實現它需要的功能,保持系統的靈活性和清晰度。
class Animal:
    def fly(self):
        return "fly"

    def run(self):
        return "run"

class Bird(Animal):
    def fly(self):
        return "fly"

不是每隻動物都會跑會飛,應該讓會飛的 Bird 去實現 fly。

依賴反轉原則 (Dependency Inversion Principle, DIP)

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
高階模組不應該依賴低階模組,兩者都應該依賴抽象介面,原始碼的依賴關係只涉及抽象不涉及具體。

優點:

  • 降低系統耦合度,模組之間不會直接依賴具體的實現,而是透過抽象,使得系統更容易維護和擴展。
class Vehicle:
    def brake(self):
        return "brake!"

class Bus:
    def __init__(self):
        self.bus = Vehicle()

    def stop_bus(self):
        return self.bus.brake()

bus = Bus()
print(bus.stop_bus()) // "brake"

Bus class 依賴 Car 的 brake,如果之後 Car 的 brake 修改了,就會導致 Bus brake 出錯。
因此可以使用 python 的 abstractmethod

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def brake(self):
        pass

class Bicycle(Vehicle):
    def brake(self):
        return "Bicycle braking!"

class Car(Vehicle):
    def brake(self):
        return "Car braking!"

class VehicleManager:
    def __init__(self, vehicle: Vehicle):
        self.vehicle = vehicle

    def brake(self):
        return self.vehicle.brake()

bicycle = Bicycle()
car = Car()

transport_manager_for_bicycle = VehicleManager(bicycle)
print(transport_manager_for_bicycle.brake())

transport_manager_for_car = VehicleManager(car)
print(transport_manager_for_car.brake())

可以看到低階模組(bicycle, car) 高階模組(vehicle) 都依賴抽象介面,再透過低階模組的實例化,來執行不同的剎車。

以上就是 SOLID 的介紹,我們明天見!

Refence:


上一篇
Day-7 | RESTful 成熟度 & HATEOAS
下一篇
Day-9 | Webserver X Nginx
系列文
埋藏在後端工程下的地雷與寶藏30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言